package web

import (
	"bytes"
	"time"

	"github.com/SermoDigital/jose/crypto"
	"github.com/SermoDigital/jose/jws"
	"github.com/SermoDigital/jose/jwt"
	http "github.com/valyala/fasthttp"

	"xp/lib/log"
	"xp/lib/mux"
	"xp/lib/mux/web/token"
)

const (
	sessionKey     = `__session`
	sessionIssuer  = `__xp_issuer`
	sessionSubject = `__xp_session`
)

var (
	hmacKey = []byte(`2nDadminNDPToken3Security1nDadminNDPToken3Security3`)
)

type Session struct {
	jwt.Claims

	valid bool
	dirty bool
}

func (s *Session) Valid() bool {
	return s.valid
}

func (s *Session) Set(key string, val interface{}) {
	s.dirty = true
	s.valid = true
	s.Claims.Set(key, val)
}

func (s *Session) Get(key string) string {
	if v := s.Claims.Get(key); v != nil {
		return v.(string)
	}
	return ""
}

func (s *Session) Reset(ctx *mux.Ctx) {
	token.Login(ctx, "")
	s.dirty = true
}

func SessionFromCtx(ctx *mux.Ctx) *Session {
	val := ctx.GetExtended(sessionKey)
	if val != nil {
		return val.(*Session)
	}
	s := &Session{Claims: make(jwt.Claims)}
	s.SetExpiration(time.Now().AddDate(0, 1, 0))
	ctx.Extend(sessionKey, s)
	return s
}

func NamedSession(name string) mux.Middleware {
	uk := name + "_u"
	return func(next mux.Handler) mux.Handler {
		return func(ctx *mux.Ctx) {
			encoded := ctx.Request.Header.Cookie(name)
			if len(encoded) != 0 {
				tk, err := jws.ParseJWT(encoded)
				if err != nil {
					log.Warn("err", err.Error())
				}
				if bytes.Equal(ctx.Method(), methodGet) && !IsAjax(ctx) {
					audiences, ok := tk.Claims().Audience()
					if ok {
						var u string
						if len(audiences) > 0 {
							u = audiences[0]
						}
						token.Login(ctx, u)
					}
				}
				if tk != nil {
					validator := new(jwt.Validator)
					validator.SetAudience(token.CurrentUser(ctx))
					validator.SetIssuer(sessionIssuer)
					validator.SetSubject(sessionSubject)
					err := tk.Validate(hmacKey, crypto.SigningMethodHS256)
					if err != nil {
						log.Warn("err", err.Error())
					}
					if err == nil {
						if _, ok := tk.Claims().Expiration(); ok {
							session := &Session{valid: true, Claims: tk.Claims()}
							ctx.Extend(sessionKey, session)
						}
					}
				}
			}

			next(ctx)
			session := SessionFromCtx(ctx)
			if !session.valid || !session.dirty {
				return
			}

			u := token.CurrentUser(ctx)
			session.Claims.SetAudience(u)
			session.Claims.SetIssuer(sessionIssuer)
			session.Claims.SetSubject(sessionSubject)
			tk := jws.NewJWT(jws.Claims(session.Claims), crypto.SigningMethodHS256)
			encoded, err := tk.Serialize(hmacKey)
			if err != nil {
				ctx.Error(err)
				return
			}

			cookie := http.AcquireCookie()
			cookie.SetKey(name)
			cookie.SetPath("/")
			cookie.SetHTTPOnly(true)
			cookie.SetValueBytes(encoded)
			host := ctx.Host()
			if index := bytes.Index(host, []byte(":")); index > 0 {
				host = host[:index]
			}
			cookie.SetDomainBytes(host)
			cookie.SetExpire(time.Now().AddDate(1, 0, 0))
			ctx.Response.Header.SetCookie(cookie)
			cookie.SetHTTPOnly(false)
			cookie.SetKey(uk)
			cookie.SetValue(u)
			ctx.Response.Header.SetCookie(cookie)
			http.ReleaseCookie(cookie)
		}
	}
}
